import scipy.optimize
import numpy as np
import datetime
from dateutil import parser

from investment.date_utilities import search_trade_day, d2m, m2d


__all__ = ["OperationLoop", "Operate",
           "calc_reward_rate", "calc_xirr", "calc_capital_earn_rate"]


class OperationLoop:
    def __init__(self, money, start_date: str, end_date: str = None, holding_month=6):
        self.money = money
        self.start_date = parser.parse(start_date)
        if end_date is not None:
            self.end_date = parser.parse(end_date)
            self.holding_month = d2m((self.end_date - self.start_date).days)
        else:
            self.holding_month = holding_month
            self.end_date = self.start_date + datetime.timedelta(days=m2d(holding_month))

    def calc_rate(self, dates, value_time_series):
        s_idx, _ = search_trade_day(dates, self.start_date)
        start_value = value_time_series[s_idx]
        e_idx, _ = search_trade_day(dates, self.end_date)
        end_value = value_time_series[e_idx]
        return calc_reward_rate(start_value, end_value)


class Operate:
    def __init__(self, money, date_in: datetime.date):
        """
        :param money: have direction : in account is "-", out account is "+"
        :param date_in: should be datetime.date
        """
        self.money = money  #
        self.date = date_in


def calc_reward_rate(start_value, end_value):
    assert abs(start_value) > 1e-5
    rate = (end_value - start_value) / start_value
    return rate


def inverse_rate_to_year(reward_rate, year):
    import math
    rate = math.e**(math.log(reward_rate)/year)
    return rate


def xnpv(rate, values, dates):
    """Equivalent of Excel's XNPV function.

    from datetime import date
    dates = [date(2010, 12, 29), date(2012, 1, 25), date(2012, 3, 8)]
    values = [-10000, 20, 10100]
    xnpv(0.1, values, dates)
    -966.4345...
    """
    if rate <= -1.0:
        return float('inf')
    d0 = dates[0]  # or min(dates)
    return sum([vi / (1.0 + rate) ** ((di - d0).days / 365.0) for vi, di in zip(values, dates)])


def calc_xirr(values, dates):
    """Equivalent of Excel's XIRR function.

    from datetime import date
    dates = [date(2010, 12, 29), date(2012, 1, 25), date(2012, 3, 8)]
    values = [-10000, 20, 10100] # note: in account is "-", out is "+"
    xirr(values, dates)
    0.0100612...
    """
    try:
        return scipy.optimize.newton(lambda r: xnpv(r, values, dates), 0.0)
    except RuntimeError:  # Failed to converge?
        return scipy.optimize.brentq(lambda r: xnpv(r, values, dates), -1.0, 1e10)


def calc_capital_earn_rate(values, dates):
    """Equivalent of Excel's XIRR function.

    from datetime import date
    dates = [date(2010, 12, 29), date(2012, 1, 25), date(2012, 3, 8)] note: increasing
    values = [-10000, 20, 10100] # note: in account is "-", out is "+"
    0.0100612...
    """
    total_earn = np.sum(values)
    total_cap = 0
    total_days = (dates[-1] - dates[0]).days
    if total_days < 0:
        assert "dates/ values should be increasing from previous to current"
    for vi, di in zip(values, dates):
        total_cap += vi * (di-dates[-1]).days / total_days
    return total_earn / total_cap


if __name__ == "__main__":
    from datetime import date
    t_dates = [date(2010, 12, 29), date(2012, 1, 25), date(2012, 3, 8)]
    t_values = [-10000, 20, 11100]
    earn = calc_capital_earn_rate(t_values, t_dates)
    xirr_earn = calc_xirr(t_values, t_dates)
    Operate(-10000., date(2010, 12, 29))
    print(earn)
